iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0

昨天已經寫完 seasongame_typeutil,我們是用字串的陣列來判斷使用者傳入的值正不正確,像是 season 是用年份可能就還算好辨別,但如果看到 game_type 的話,用每個英文的頭一個字當簡寫可能就沒辦法那麼清楚。再來就是這兩個都能直接用一個值代表,但如果是 month 的話,因為他會有兩個月份合在一起的選項,這樣就不能直接用數字代替,可能就要用 key & valuedictionary 來儲存代表的數字,像是:

month_map = {
  "Mar/Apr": 4,
  "May": 5,
  # ... 其他月份
}

這樣的話如果要使用指定月份的值,就只要 month_map["Mar/Apr"],而如果要檢查是否在可使用的月份內的話就可以用 month in month_map.keys()
雖然 dictionary 能達到我們多數的需求,但還是存在一些問題像是容易被修改數值,可能一個不小心就會在哪邊把對應的 key 裡面的 value 改掉。又或是在定義的時候,不小心用到重複的 key 值,會直接覆蓋比較舊的值。

month_map = {
  "Mar/Apr": 4,
  "May": 5,
  # ... 其他月份
}

# 會把原本的 5 改成 "May" 了
month_map["May"] = "May"

# 最後 month_map["May"] 會等於 6,而不是 5
month_map = {
  "Mar/Apr": 4,
  "May": 5,
  "May": 6
  # ... 其他月份
}

因此像是月份這種固定的常數,會更傾向於用 Enum 來儲存。

Enum

要建立一個 Enum 非常簡單,只要 import Python 內建套件裡的 enum,再來使用 class 來宣告就好,用我們的 month 當範例就可以寫成,記得開另外開一個資料夾 enums 儲存:

# src/baseball_stats_python/enums/statcast.py
from enum import Enum


class Month(Enum):
    MARCH_AND_APRIL = "4"
    MAY = "5"
    JUNE = "6"
    JULY = "7"
    AUGUST = "8"
    SEPTEMBER_AND_OCTOBER = "9"

因為常數的關係,會習慣使用全大寫跟 _ 來分隔單字,Enum 也不會剛剛提到的 dictionary 的問題,如果我們不小心重複多加了另一個 AUGUST,系統就會有提示說已經有同樣的 member 了。
https://ithelp.ithome.com.tw/upload/images/20241005/20163024uxU3xm0EeC.png

建立完一個 Enum,直接輸入 Month.MARCH_AND_APRIL 就可以使用一個型態為 <enum 'Month'> 的值,裡面可以用 .name 獲得 key 值,然後用 .value 取得裡面的值。

https://ithelp.ithome.com.tw/upload/images/20241005/20163024G4o4A8N4pA.png

我們之後的判斷都會使用 .value,不過這樣要每次都要多打很麻煩,就可以多加 [str] 修改預設值(https://docs.python.org/zh-tw/3/library/enum.html#enum.Enum.str):

class Month(Enum):
    MARCH_AND_APRIL = "4"
    MAY = "5"
    JUNE = "6"
    JULY = "7"
    AUGUST = "8"
    SEPTEMBER_AND_OCTOBER = "9"

    def __str__(self):
        return self.value

這樣在使用 Month.MARCH_AND_APRIL 就會直接得到 4

接下來再來設置檢查使用者輸入的值,是否有在我們設定的 Enum 裡面,這邊會用到 Enum 裡的 _value2member_map_,以及運用 class 的特性,設定一個 classmethod,讓我們可以使用 Month.has_value 這樣的方式來檢查,寫法如下:

class Month(Enum):
    MARCH_AND_APRIL = "4"
    MAY = "5"
    JUNE = "6"
    JULY = "7"
    AUGUST = "8"
    SEPTEMBER_AND_OCTOBER = "9"

    def __str__(self):
        return self.value

    @classmethod
    def has_value(cls, value):
        return value in cls._value2member_map_

有了這些,就能來寫我們的 Util

from enums.statcast import Month


def get_month_param_str(month: str | list[str]) -> str:
    if (type(month) != str and type(month) != list):
        raise ValueError(f"Invalid type for month: {month}")

    if (type(month) == list):
        if any(not Month.has_value(month) for month in month):
            raise ValueError(f"Invalid months: {'|'.join(month)}")
        return f"{'|'.join(month)}|"
        
    if (month == ""):
        return ""

    if (month == "all"):
        return f"{'|'.join(month)}|"

    if (not Month.has_value(month)):
        raise ValueError(f"Invalid month: {month}")

    return f"{month}|"

記得要使用到 EnumMonth 要從 enums 資料夾 import,接著就可以用 Month.has_value 來檢查是否合法,剩下都差不多,他跟 game_type 一樣最後要加 |,然後因為 month 預設是空字串,空字串就不用最後加 |,也要另外處理。

本日小結

今天算簡單介紹了一下 Enum 的用法,他其實可以做其他很多延伸,大家可以去試著玩玩看,pybaseball 也有用到,也可以參考看看。有了 Enum 剩下的 params util 都可以順利完成,明天應該就會來測試上傳到 PyPI 有沒有問題,篇幅夠的話會來寫 README 文件。

最後一樣感謝大家耐心地看完這篇文章,有任何問題或建議都歡迎留言告訴我,明天見,掰掰。


上一篇
Day 20 - Utils Functions
下一篇
Day 22 - Enum 延伸與套件 Import
系列文
上次介紹的棒球套件很少更新了,那就只好自己寫一個!?31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言